﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Transactions;
using System.Web;
using Common.Logging;
using NPMonitor.DataModel;
using NPMonitor.Helpers;
using NPMonitor.Implementation.Internal;
using NPMonitor.Interfaces;

namespace NPMonitor.Implementation.Store
{
    public class JobDefinitionStore : IJobDefinitionStore
    {
        #region Properties

        public ILog Logger
        {
            get { return LogManager.GetCurrentClassLogger(); }
        }

        #endregion

        #region IJobDefinitionStore Members

        public IList<JobDefinition> GetAllJobDefinitions(Expression<Func<JobDefinition, bool>> queryExpression)
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.JobDefinitions.Where(queryExpression).OrderBy(j => j.JobId).ToList();
            }
        }

        public List<JobDefinition> GetAllJobDefinitions(int pageIndex = 0, int pageSizes = int.MaxValue)
        {
            List<JobDefinition> jobList = new List<JobDefinition>();

            if (pageIndex < 0)
            {
                pageIndex = 0;
            }

            if (pageSizes < 0)
            {
                pageSizes = int.MaxValue;
            }

            using (var ctx = new DbStoreContext())
            {
                var query = from p in ctx.JobDefinitions
                            select p;
                jobList = query.OrderByDescending(x => x.JobId).Skip(pageIndex * pageSizes).Take(pageSizes).ToList();
            }

            return jobList;
        }

        /// <summary>
        /// Retrieve Job File Info From posted file
        /// </summary>
        /// <param name="postedFile"></param>
        /// <returns></returns>
        public JobFileInfo RetrieveJobFileInfo(HttpPostedFile postedFile)
        {
            string tmpFilePath = FileManager.SaveAsTempFile(postedFile);  // save temp file

            JobFileInfo jobFileInfo = JobFileLoader.GetJobFileInfo(tmpFilePath); // retrieve job file info by temp file

            return jobFileInfo;
        }

        public void SaveJobDefinition(JobDefinition jobDefinition, string tmpFileName, params Scenario[] scenarios)
        {
            using (var trans = new TransactionScope())
            {
                string fileName = tmpFileName;

                if (!String.IsNullOrEmpty(tmpFileName))
                {
                    string filePath = FileManager.SaveUploadedFileByTempFile(tmpFileName);

                    fileName = Utils.GetFileNameByPath(filePath);
                }

                if (!String.IsNullOrEmpty(fileName))
                {
                    jobDefinition.FileName = fileName;
                }

                using (var ctx = new DbStoreContext())
                {
                    this.SaveJobDefinition(jobDefinition);

                    // Clear original scenario
                    if (scenarios.Length > 0)
                    {
                        if (jobDefinition.JobId != null && jobDefinition.JobId != 0)
                        {
                            ClearScenariosByJobId(jobDefinition.JobId.Value);
                        }

                        this.AddScenarios(jobDefinition.JobId.Value, scenarios.ToArray());
                    }

                    trans.Complete();
                }
            }
        }

        public void SaveJobDefinition(JobDefinition jobDefinition)
        {
            using (var ctx = new DbStoreContext())
            {
                if (jobDefinition.JobId == null)
                {
                    jobDefinition.CreateTime = Helpers.Utils.CurrentServerUtcTime;
                    jobDefinition.CreateBy = "Dev";

                    ctx.Entry(jobDefinition).State = EntityState.Added;
                    ctx.JobDefinitions.Add(jobDefinition);
                }
                else
                {
                    JobDefinition origJobDefinition = GetJobDefinitionById(jobDefinition.JobId.Value);

                    origJobDefinition.JobName = jobDefinition.JobName == null ? origJobDefinition.JobName : jobDefinition.JobName;
                    origJobDefinition.Desc = jobDefinition.Desc == null ? origJobDefinition.Desc : jobDefinition.Desc;
                    origJobDefinition.TypeName = jobDefinition.TypeName == null ? origJobDefinition.TypeName : jobDefinition.TypeName;
                    origJobDefinition.Version = jobDefinition.Version == null ? origJobDefinition.Version : jobDefinition.Version;
                    origJobDefinition.FileName = jobDefinition.FileName == null ? origJobDefinition.FileName : jobDefinition.FileName;

                    origJobDefinition.UpdateTime = Helpers.Utils.CurrentServerUtcTime;
                    origJobDefinition.UpdateBy = Utils.GetCurrentUser();

                    ctx.Entry(origJobDefinition).State = EntityState.Modified;

                    jobDefinition = origJobDefinition;
                }

                ctx.SaveChanges();
            }
        }

        public void DeleteJobDefinitionById(int id)
        {
            using (var ctx = new DbStoreContext())
            {
                var jobToDelete = ctx.JobDefinitions.Find(id);

                if (jobToDelete != null)
                {
                    FileManager.DeleteUploadedFile(jobToDelete.FileName);

                    using(var trans = new TransactionScope())
                    {
                        ClearScenariosByJobId(id);
                        ctx.JobDefinitions.Remove(jobToDelete);
                        ctx.SaveChanges();
                        trans.Complete();
                    }
                }
            }
        }

        public int GetJobDefinitionTotalCount()
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.JobDefinitions.Count();
            }
        }

        public JobDefinition GetJobDefinitionByJobName(string jobName)
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.JobDefinitions.FirstOrDefault(x => x.JobName == jobName);
            }
        }
       
        /// <summary>
        /// 根据job name模糊查询
        /// </summary>
        /// <param name="foggyName"></param>
        /// <returns></returns>
        public List<JobDefinition> GetJobDefinitionByFoggyName(string foggyName)
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.JobDefinitions.Where(x => x.JobName.Contains(foggyName)).ToList();
            }
        }   
        /// <summary>
        /// 根据job id 获取job 定义信息
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public JobDefinition GetJobDefinitionById(int id)
        {
            using (DbStoreContext ctx = new DbStoreContext())
            {
                return ctx.JobDefinitions.FirstOrDefault(p => p.JobId == id);
            }
        }

        public ValidationResult ValidateJobDefinition(JobDefinition jobDefinition)
        {
            if (jobDefinition.JobId != null && jobDefinition.JobId != 0)
            {
                if (String.IsNullOrEmpty(jobDefinition.JobName))
                {
                    return new ValidationResult(false, "Require job definition job name", "REQ_JobName");
                }

                if (String.IsNullOrEmpty(jobDefinition.FileName))
                {
                    return new ValidationResult(false, "Require job definition file name", "REQ_FileName");
                }

                if (String.IsNullOrEmpty(jobDefinition.TypeName))
                {
                    return new ValidationResult(false, "Require job definition type name", "REQ_TypeName");
                }
            }

            if (jobDefinition.Desc != null && jobDefinition.Desc.Length > 1000)
            {
                return new ValidationResult(false, "Description size can't exceeds 1000 characters.", "EXD_Description");
            }

            using (var ctx = new DbStoreContext())
            {
                var query = ctx.JobDefinitions.Where(jd =>
                    (jd.JobName == jobDefinition.JobName || jd.TypeName == jobDefinition.TypeName || jd.FileName == jobDefinition.FileName));

                // Can't retrieve Scenario with id compare, because EF can't generat sql currently, if id could be null
                IList<JobDefinition> dbJobs = query.ToList();

                JobDefinition dbJob = dbJobs.FirstOrDefault(s => s.JobId == jobDefinition.JobId);

                if (dbJob != null)
                {
                    if (new Version(jobDefinition.Version) <= new Version(dbJob.Version))
                    {
                        return new ValidationResult(false, "Uploaded job version (" + jobDefinition.Version + ") should be higher than current job version(" + dbJob.Version + ").", "LOW_JobVersion");
                    }
                }

                dbJob = dbJobs.FirstOrDefault(s => s.JobId != jobDefinition.JobId);

                if (dbJob != null)
                {
                    if (String.Compare(jobDefinition.JobName, dbJob.JobName, true) == 0)
                    {
                        return new ValidationResult(false, "Job name : \"" + jobDefinition.JobName + "\" already exists.", "DUP_TypeName");
                    }

                    if (String.Compare(jobDefinition.TypeName, dbJob.TypeName, true) == 0)
                    {
                        return new ValidationResult(false, "Type name : \"" + jobDefinition.TypeName + "\" already exists.", "DUP_TypeName");
                    }
                    
                    if (String.Compare(jobDefinition.FileName, dbJob.FileName, true) == 0)
                    {
                        return new ValidationResult(false, "File name : \"" + jobDefinition.FileName + "\"  already exists.", "DUP_FileName");
                    }
                }
            }

            return new ValidationResult(true);
        }

        public IList<Scenario> GetAllScenarios(Expression<Func<Scenario, bool>> queryExpression)
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.Scenarios.Where(queryExpression).OrderBy(s => s.Id).ToList();
            }
        }

        public ValidationResult ValidateScenario(Scenario scenario)
        {
            if (String.IsNullOrEmpty(scenario.ScenarioNO))
            {
                return new ValidationResult(false, "Scenario NO can't be empty.", "REQ_ScenarioNO");
            }

            if (scenario.ScenarioNO.Length > 50)
            {
                return new ValidationResult(false, "Scenario NO size can't exceeds 50 characters.", "EXD_ScenarioNO");
            }

            if (String.IsNullOrEmpty(scenario.ScenarioName))
            {
                return new ValidationResult(false, "Scenario Name can't be empty.", "REQ_ScenarioName");
            }

            if (scenario.ScenarioName.Length > 200)
            {
                return new ValidationResult(false, "Scenario Name size can't exceeds 200 characters.", "EXD_ScenarioName");
            }

            if (scenario.URL != null && scenario.URL.Length > 1000)
            {
                return new ValidationResult(false, "URL size can't exceeds 1000 characters.", "EXD_URL");
            }

            if (scenario.Description != null && scenario.Description.Length > 1000)
            {
                return new ValidationResult(false, "Description size can't exceeds 1000 characters.", "EXD_Description");
            }

            using (var ctx = new DbStoreContext())
            {
                var query = ctx.Scenarios.Where(s =>
                    s.ScenarioNO == scenario.ScenarioNO
                    || s.ScenarioName == scenario.ScenarioName);

                // Can't retrieve Scenario with id compare, because EF can't generat sql currently, if id could be null
                IList<Scenario> dbScenarios = query.ToList();

                Scenario dbScenario = dbScenarios.FirstOrDefault(s => s.Id != scenario.Id);

                if (dbScenario != null)
                {
                    if (scenario.Id != null && scenario.Id != 0 && dbScenario.Id != scenario.Id)
                    {
                        if (String.Compare(scenario.ScenarioNO, dbScenario.ScenarioNO, true) == 0)
                        {
                            return new ValidationResult(false, "Scenario NO already exists.", "DUP_ScenarioNO");
                        }
                        else if (String.Compare(scenario.ScenarioName, dbScenario.ScenarioName, true) == 0)
                        {
                            return new ValidationResult(false, "Scenario Name already exists.", "DUP_ScenarioName");
                        }
                    }
                }
            }

            return new ValidationResult(true);
        }

        public void SaveScenario(Scenario scenario)
        {
            using (var ctx = new DbStoreContext())
            {
                if (scenario.Id == null)
                {
                    ctx.Entry(scenario).State = EntityState.Added;
                    ctx.Scenarios.Add(scenario);
                }
                else
                {
                    ctx.Entry(scenario).State = EntityState.Modified;
                }

                ctx.SaveChanges();
            }
        }

        public int GetScenariosTotalCount(int? jobId)
        {
            int count = 0;

            using (var ctx = new DbStoreContext())
            {
                if (jobId == null)
                {
                    count = ctx.Scenarios.Count();
                }
                else
                {
                    count = ctx.Scenarios.Count(s => s.JobId == jobId.Value);
                }
            }

            return count;
        }

        public Scenario GetScenario(int scenarioId)
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.Scenarios.FirstOrDefault(s => s.Id == scenarioId);
            }
        }

        public Scenario GetScenarioByNo(string scenarioNo)
        {
            using (var ctx = new DbStoreContext())
            {
                return ctx.Scenarios.FirstOrDefault(s => s.ScenarioNO == scenarioNo);
            }
        }

        public IList<Scenario> GetScenarios(int? jobId, int pageIndex = 0, int pageSizes = int.MaxValue)
        {
            IList<Scenario> scenarios = new List<Scenario>();

            if (pageIndex < 0)
            {
                pageIndex = 0;
            }

            if (pageSizes < 0)
            {
                pageSizes = int.MaxValue;
            }

            using (var ctx = new DbStoreContext())
            {
                IQueryable<Scenario> query;

                if (jobId == null)
                {
                    query = from s in ctx.Scenarios
                            select s;
                }
                else
                {
                    query = from p in ctx.Scenarios
                            where p.JobId == jobId.Value
                            select p;
                }

                scenarios = query.OrderBy(x => x.JobId).Skip(pageIndex * pageSizes).Take(pageSizes).ToList();
            }

            return scenarios;
        }

        public IList<Scenario> GetEnableScenarios(int jobId)
        {
            IList<Scenario> scenarios = new List<Scenario>();
            using (var ctx = new DbStoreContext())
            {
                var query = from p in ctx.Scenarios
                            where p.JobId == jobId && p.Enabled == true
                            select p;
                scenarios = query.ToList();
            }

            return scenarios;
        }

        #endregion

        #region Private Methods

        private void AddScenarios(int jobId, params Scenario[] scenarios)
        {
            using (TransactionScope ts = new TransactionScope())
            {
                using (var ctx = new DbStoreContext())
                {
                    foreach (var s in scenarios)
                    {
                        s.JobId = jobId;
                        ctx.Entry(s).State = EntityState.Added;
                    }

                    ctx.SaveChanges();
                }

                ts.Complete();
            }
        }

        private void ClearScenariosByJobId(int jobId)
        {
            IList<Scenario> origScenarios = GetScenarios(jobId);

            using (var ctx = new DbStoreContext())
            {
                foreach (var s in origScenarios)
                {
                    ctx.Entry(s).State = EntityState.Deleted;

                    ctx.Scenarios.Remove(s);
                }

                ctx.SaveChanges();
            }
        }

        #endregion
    }
}
